﻿/*
 * Created by SharpDevelop.
 * User: ivo.stoykov
 * Date: 31.10.2013 г.
 * Time: 14:19
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;

namespace duplica
{
  class Program
  {
    
    static private int DuplicaNums { get; set; }
    static private int Start {get; set; }
    static private Boolean Prepend { get; set; }
    static private Boolean Verbose {get; set; }
    static private Boolean UseTimeStamp {get; set; }
    static private Boolean RenameSource {get; set;}
    static private String CounterFormat {get; set; }
    static private String DestPath { get; set; }
    static private String DestFile { get; set; }
    static private String FileExt { get; set; }
    
    static Dictionary<string, string> prmts = new Dictionary<string, string>() {
      {"n", "1"}, {"number-of-times", "1"}, {"t", ""}, {"timestamp", ""},
      {"c", ""}, {"counter-start", ""}, {"x", ""}, {"no-leading-zero", ""},
      {"p", ""}, {"prepend", ""}, {"v", ""}, {"verbose", ""}, {"h", ""},
      {"r", "on"}, {"rename-source", "on"}, {"help", ""},
      {"source", ""}, {"destination", ""}
    };
    
    /// <summary>
    /// Main method. A must ;-)
    /// </summary>
    /// <param name="args">see help</param>
    public static void Main(string[] args)
    {
      if(!checkArgs(args)) { return; }
      try
      {
        DistribArgs(new List<String>(args));
        Initialize();
        DoDuplicate();
      }
      catch (Exception e) {
        Console.WriteLine(e.Message);
        Finished();
        ShowHelp();
        return;
      }
      Finished(true);
    }

    /// <summary>
    /// Standard note called from different methods
    /// </summary>
    /// <param name="isDone">if true will show message that proccess has finished.</param>
    static bool Finished(Boolean isDone=false)
    {
      Console.Write("{0}Press any key to continue . . .", (isDone ? Environment.NewLine + "DUPLICA done."+Environment.NewLine: String.Empty));
      ConsoleKeyInfo key = Console.ReadKey();
      return key.KeyChar == 'q';
      
    }

    /// <summary>
    /// Properties initialization based on supplied parameters
    /// </summary>
    static void Initialize()
    {
      CounterFormat = String.IsNullOrEmpty(prmts["x"]) && String.IsNullOrEmpty(prmts["no-leading-zero"]) 
                      ? "{0:D"+ Math.Max(prmts["c"].Length, prmts["counter-start"].Length)+"}" : "{0}";
      Verbose = !String.IsNullOrEmpty(prmts["v"]) || !String.IsNullOrEmpty(prmts["verbose"]);
      Prepend = !String.IsNullOrEmpty(prmts["p"]) || !String.IsNullOrEmpty(prmts["prepend"]);
      UseTimeStamp = !String.IsNullOrEmpty(prmts["t"]) || !String.IsNullOrEmpty(prmts["timestamp"]);
      bool useCounter = !String.IsNullOrEmpty(prmts["c"]) || !String.IsNullOrEmpty(prmts["counter-start"]);
      DuplicaNums = 1;
      if(!prmts["n"].Trim().Equals("1")) {  DuplicaNums = Convert.ToInt32(prmts["n"]); }
      if(!prmts["number-of-times"].Trim().Equals("1")) { DuplicaNums = Convert.ToInt32(prmts["number-of-times"]); }
      Start = 0;
      if( useCounter || !UseTimeStamp ) // if counter-start is supplied, timestamp is ignored
      {
        Start = Math.Max(Convert.ToInt32(String.IsNullOrEmpty(prmts["c"]) ? "1" : prmts["c"]), 
                         Convert.ToInt32(String.IsNullOrEmpty(prmts["counter-start"]) ? "1" : prmts["counter-start"]));
        UseTimeStamp = false;
      }
      RenameSource = prmts["r"].ToLower().Equals("on") && prmts["rename-source"].ToLower().Equals("on");
      MakeDestination();
    }
    
    /// <summary>
    /// Actual duplication is done here
    /// </summary>
    static void DoDuplicate()
    {
      int stop = Start +  DuplicaNums;
      for (int i = Start; i < stop; i++)
      {
        string uniqueNum = String.Format(CounterFormat, i);
        if(UseTimeStamp)
        {
          uniqueNum = DateTime.Now.ToString("yyyyMMddhhmmssffff");
          Thread.Sleep(10);
        }
        string newFile =  Prepend ? String.Format("{0}_{1}", uniqueNum, DestFile) : DestFile.Replace(FileExt, String.Format("_{0}{1}", uniqueNum, FileExt));
        File.Copy(prmts["source"], Path.Combine(DestPath, newFile));
        if (Verbose) {  Console.WriteLine("{0} duplicated as {1}.", prmts["source"], Path.Combine(DestPath, newFile));  }
      }
      if(RenameSource) {  File.Delete(prmts["source"]);  }
    }
    
    /// <summary>
    /// Compose destination full path name dependin on the value of source and destination parameters' values.
    /// </summary>
    static void MakeDestination()
    {
      if(String.IsNullOrEmpty(prmts["destination"]))   // if there is no destination
      {
        StparateFileParts(prmts["source"]);
        return;
      }
      if(File.Exists(prmts["destination"]))    // destination is an existing file
      {
        StparateFileParts(prmts["destination"]);
        return;
      }
      if(Directory.Exists(prmts["destination"]))  // destination is an existing folder
      {
        DestPath = prmts["destination"];
        DestFile = Path.GetFileName(prmts["source"]);
        FileExt = Path.GetExtension(prmts["source"]);
        return;
      }
      if(Regex.IsMatch(prmts["destination"], @"\.\w{1,}$"))  // only file name (not exists) 
      {
        DestFile = Path.GetFileName(prmts["destination"]);
        FileExt = Path.GetExtension(DestFile);
        DestPath =  prmts["destination"].Replace(DestFile, String.Empty);
        if(String.IsNullOrEmpty(DestPath)) {  DestPath = prmts["source"].Replace(Path.GetFileName(prmts["source"]), String.Empty);  }
        return;
      }
      if(String.IsNullOrEmpty(DestPath)) { throw new InvalidEnumArgumentException("Destination path is missing or not valid."); }
      if(String.IsNullOrEmpty(DestFile)) { throw new InvalidEnumArgumentException("Destination file is missing or not valid."); }
    }

    /// <summary>
    /// Separate file from folder and get extension
    /// </summary>
    /// <param name="thePath">string representing full destination path</param>
    static void StparateFileParts(String thePath)
    {
      DestPath = thePath;
      DestFile = Path.GetFileName(DestPath);
      FileExt = Path.GetExtension(DestPath);
      DestPath = DestPath.Replace(DestFile, String.Empty);
      return;
    }
    
    /// <summary>
    /// Validation and alanysis of supplied parameters and values.
    /// </summary>
    /// <param name="args">args array from main module</param>
    static void DistribArgs(List<string> args)
    {
      while (args.Count>0)
      {
        if(args[0].Contains("="))
        {
          string[] keyVal = args[0].ToLower().Split('=');
          ValidateParam(keyVal[0].Replace("--", String.Empty));
          prmts[keyVal[0].Replace("--", String.Empty)] = keyVal[1];
          if(String.IsNullOrEmpty(keyVal[1]))
          {
            throw new InvalidEnumArgumentException(String.Format("Parameter [{0}] has invalid value - [{1}]", keyVal[0], keyVal[1]));
          }
          args.RemoveAt(0);
          continue;
        }
        if(args[0].ToLower().Equals("-c") || args[0].ToLower().Equals("-n") || args[0].ToLower().Equals("-r"))
        {
          ValidateParam(args[0].ToLower().Replace("-", String.Empty));
          prmts[args[0].ToLower().Replace("-", String.Empty)] = args[1];
          if(String.IsNullOrEmpty(args[1]) || args[1].StartsWith("-"))
          {
            throw new InvalidEnumArgumentException(String.Format("Parameter [{0}] has invalid value - [{1}]", args[0], args[1]));
          }
          args.RemoveRange(0, 2);
          continue;
        }
        if(args[0].StartsWith("-"))
        {
          string key = Regex.Replace(args[0].ToLower(), @"^\-{1,}", String.Empty);
          ValidateParam(key);
          prmts[key] = "+";
          args.RemoveAt(0);
          continue;
        }
        if(File.Exists(args[0]))
        {
          prmts["source"] = args[0];
          args.RemoveAt(0);
        }
        else
        {
          prmts["destination"] = args[0];
          args.RemoveAt(0);
        }
      }
      if(String.IsNullOrEmpty(prmts["source"])) {  throw new InvalidEnumArgumentException("Missing source file.");  }
      if(!File.Exists(prmts["source"])) {  throw new InvalidEnumArgumentException(String.Format("File [{0}] dies not exists.", prmts["source"]));  }
    }
    
    /// <summary>
    /// Check whether supplied parameter is valid
    /// </summary>
    /// <param name="par">parameter name to check for</param>
    static void ValidateParam(string par)
    {
      if(!prmts.ContainsKey(par))
      {
        throw new InvalidEnumArgumentException(String.Format("Unknown argument - {0}", par));
      }
    }
    
    /// <summary>
    /// Check whether supplied parameter are enough - at least source is required.
    /// Check for help parameter. If supplied only help message is shown and execution is interruped.
    /// </summary>
    /// <param name="args">arguments from main methond</param>
    /// <returns>return true if there is no help parameter and at least another one has been supplied.</returns>
    static bool checkArgs(string[] args)
    {
      if(String.Join(" ", args).IndexOf("-h", StringComparison.InvariantCultureIgnoreCase)>-1 ||
         String.Join(" ", args).IndexOf("?", StringComparison.InvariantCultureIgnoreCase)>-1)
      {
        ShowHelp();
        return false;
      }
      if(args.Length == 0)
      {
        ShowHelp();
        return false;
      }
      return true;
    }
    
    
    /// <summary>
    /// Show usage help
    /// </summary>
    public static void ShowHelp()
    {
      string msg =
        @"Duplicate given file N times.

DUPLICA [options] source [destination]

Options:
  -n, --number-of-times=NUMBER   duplicate source NUMBER times
                                 into the destination starting
                                 from counter-start value (see -c).
  -t, --timestamp                add time stamp to the name
                                 for each new coopy
  -c, --counter-start=NUMBER     add counter-start starting from NUMBER,
                                 instead a time stam (see -t option).
                                 It obeys NUMBER positions unless
                                 -x, --no-leading-zero is applied.
                                 If both -c and -t optioins are set
                                 -c (counter-start) will be used.
                                 If parameter missed counter-start will be 1.
  -x, --no-leading-zero          Deny zeroes in empty position if
                                 counter-start applyed (-c option).
  -p, --prepend                  prepend unique number
                                 (see -t or -c option).
                                 If option not presented, the number
                                 will be appended (by defaut).
  -r, --rename-source=on         Switch renaming source file.(on by defaut).
                                 If --rename-source=on and destination is
                                 different than source, than source will be
                                 moved to destination.
                                 If destination filename is different than
                                 source, than source will be renamed and moved.
  -v, --verbose                  display operation info (off by default).
  -h, --help                     display this message.

  source                         file that will be duplicated.
  destination                    destination where duplicated
                                 files will be paced. If missing
                                 file(s) will be placed where the
                                 source is.

Thank you for choosing DUPLICA version.
Report bugs to https://duplica.codeplex.com/workitem/list/basic
";
      Version v = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
      msg = msg.Replace("version", String.Format("version {0}.{1}.{2}{3}", v.Major, v.Minor, v.Build, Environment.NewLine));
      string[] lines = msg.Replace(Environment.NewLine, "@").Split('@');
      Console.Clear();
      for (int i = 0; i < lines.Length; i++) {
        Console.WriteLine(lines[i]);
        if(i > 0 && i % 20 == 0){
          if(Finished()) {  return;  }
          Console.SetCursorPosition(0, i); // delete last "press bla bla...
          Console.WriteLine();
        }
      }
      Finished();
    }
  }
}
